package com.autoool.gateway.outbound.httpclient;

import com.autoool.gateway.filter.HeaderHttpResponseFilter;
import com.autoool.gateway.filter.HttpRequestFilter;
import com.autoool.gateway.filter.HttpResponseFilter;
import com.autoool.gateway.outbound.httpclient4.NamedThreadFactory;
import com.autoool.gateway.router.HttpEndpointRouter;
import com.autoool.gateway.router.RandomHttpEndpointRouter;
import com.autoool.utils.HttpClientUtil;
import com.autoool.utils.OkHttpUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
import org.apache.http.protocol.HTTP;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.stream.Collectors;

import static io.netty.handler.codec.http.HttpResponseStatus.NO_CONTENT;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

public class HttpClientOutboundHandler {

    private final Logger logger = LoggerFactory.getLogger(HttpClientOutboundHandler.class);

    private ExecutorService proxyService;
    private List<String> backendUrls;

    HttpResponseFilter filter = new HeaderHttpResponseFilter();
    HttpEndpointRouter router = new RandomHttpEndpointRouter();

    public HttpClientOutboundHandler(List<String> backendUrls) {
        this.backendUrls = backendUrls.stream().map(this::formatUrl).collect(Collectors.toList());
        int cores = Runtime.getRuntime().availableProcessors();
        long keepAliveTime = 1000;
        int queueSize = 2048;
        RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();//.DiscardPolicy();
        proxyService = new ThreadPoolExecutor(cores, cores,
                keepAliveTime, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(queueSize),
                new NamedThreadFactory("proxyService"), handler);
    }

    public void handle(final FullHttpRequest fullRequest, final ChannelHandlerContext ctx, HttpRequestFilter filter) {
        String backendUrl = router.route(this.backendUrls);
        logger.debug("router url {}", backendUrl);
        final String url = backendUrl + fullRequest.uri();
        filter.filter(fullRequest, ctx);
        proxyService.submit(() -> fetchGet(fullRequest, ctx, url));
    }


    private String formatUrl(String backend) {
        return backend.endsWith("/") ? backend.substring(0, backend.length() - 1) : backend;
    }


    private void fetchGet(final FullHttpRequest fullHttpRequest, final ChannelHandlerContext ctx, final String url) {
        Map<String, Object> headers = new HashMap<>();
        headers.put(HTTP.CONN_DIRECTIVE, HTTP.CONN_KEEP_ALIVE);
        headers.put("mao", fullHttpRequest.headers().get("mao"));
        try {
            String response = HttpClientUtil.doGet(url, headers, Collections.emptyMap());
            if (StringUtils.isEmpty(response)) {
                handlerException(fullHttpRequest, ctx, null);
            } else {
                handleReponse(fullHttpRequest, ctx, response);
            }
        } catch (URISyntaxException | IOException e) {
            handlerException(fullHttpRequest, ctx, e.getCause());
        }
    }

    private void handleReponse(FullHttpRequest fullHttpRequest, ChannelHandlerContext ctx, String responseData) {
        FullHttpResponse response = null;
        byte[] body = responseData.getBytes(StandardCharsets.UTF_8);
        response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(body));
        response.headers().set("Content-Type", "application/json");
        response.headers().setInt("Content-Length", responseData.length());
        filter.filter(response);
        handleHttpResponse(fullHttpRequest, ctx, response);
        ctx.flush();
    }


    private void handlerException(FullHttpRequest fullHttpRequest, ChannelHandlerContext ctx, Throwable cause) {
        if (null != cause) {
            cause.printStackTrace();
        }
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, NO_CONTENT);
        handleHttpResponse(fullHttpRequest, ctx, response);
        ctx.close();
    }

    private void handleHttpResponse(FullHttpRequest fullHttpRequest, ChannelHandlerContext ctx, FullHttpResponse response) {
        if (fullHttpRequest != null) {
            if (!HttpUtil.isKeepAlive(fullHttpRequest)) {
                ctx.write(response).addListener(ChannelFutureListener.CLOSE);
            } else {
                ctx.write(response);
            }
        }
    }


}
